/*************************************************
 * ThinkJot V2
 * Author: Jeswin P. (jeswin@process64.com)
 * Website: http://www.process64.com/thinkjot/
 * This code is distributed under the terms of the
 * Apache License, Version 2.0.
 * **********************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace ThinkJot.Core.Blogs.Providers.Xml
{
    public class SerializationManager
    {
        static int discardLockPeriod = 300000;   //300 seconds, 5 minutes
        static int discardCacheEntryPeriod = 540000; //540 seconds, 9 minutes
        static int runCacheManagementInterval = 60000;   //60 seconds, 1 minute
        static int maxCacheEntryCount = 100;

        static DateTime lastCacheManagementTime = DateTime.Now;
        static object globalLock = new object();

        class FileInfo
        {
            object cacheEntry;
            public object CacheEntry
            {
                get { return cacheEntry; }
                set { cacheEntry = value; }
            }

            DateTime cachedEntryAccessTime;
            public DateTime CachedEntryAccessTime
            {
                get { return cachedEntryAccessTime; }
                set { cachedEntryAccessTime = value; }
            }
        }

        class LockInfo
        {
            ReaderWriterLock rwLock;
            public ReaderWriterLock RwLock
            {
                get { return rwLock; }
                set { rwLock = value; }
            }

            DateTime lockRequestTime;
            public DateTime LockRequestTime
            {
                get { return lockRequestTime; }
                set { lockRequestTime = value; }
            }
        }

        //Files, whose deserialized form is fully cached
        static Dictionary<string, SerializationManager.FileInfo> managedFiles = new Dictionary<string, FileInfo>();
        
        //Locks which are cached/stored
        static Dictionary<string, SerializationManager.LockInfo> managedLocks = new Dictionary<string, LockInfo>();

        public static ReaderWriterLock GetLock(string fileName)
        {
            RunCacheManagement();
            lock(globalLock)
            {
                if (managedLocks.ContainsKey(fileName))
                {
                    SerializationManager.LockInfo lockInfo = managedLocks[fileName];

                    if (lockInfo.RwLock == null)
                        lockInfo.RwLock = new ReaderWriterLock();

                    lockInfo.LockRequestTime = DateTime.Now;
                    return lockInfo.RwLock;
                }
                else
                {
                    SerializationManager.LockInfo lockInfo = new LockInfo();
                    lockInfo.RwLock = new ReaderWriterLock();
                    managedLocks[fileName] = lockInfo;
                    lockInfo.LockRequestTime = DateTime.Now;
                    return lockInfo.RwLock;
                }
            }
        }

        public static object GetObjectFromCache(string fileName)
        {
            RunCacheManagement();
            lock (globalLock)
            {
                if (managedFiles.ContainsKey(fileName))
                {
                    managedFiles[fileName].CachedEntryAccessTime = DateTime.Now;
                    return managedFiles[fileName].CacheEntry;
                }
                return null;
            }
        }

        public static void RemoveObjectFromCache(string fileName)
        {
            RunCacheManagement();
            lock (globalLock)
            {
                if (managedFiles.ContainsKey(fileName))
                {
                    managedFiles[fileName].CacheEntry = null;
                }
            }
        }

        public static void StoreInCache(string fileName, object instance)
        {
            RunCacheManagement();
            if (managedFiles.Count > maxCacheEntryCount)
                return;

            lock (globalLock)
            {
                if (managedFiles.ContainsKey(fileName))
                {
                    managedFiles[fileName].CachedEntryAccessTime = DateTime.Now;
                    managedFiles[fileName].CacheEntry = instance;
                }
                else
                {
                    SerializationManager.FileInfo fileInfo = new FileInfo();
                    fileInfo.CacheEntry = instance;
                    fileInfo.CachedEntryAccessTime = DateTime.Now;
                    managedFiles[fileName] = fileInfo;
                }
            }
        }

        private static void RunCacheManagement()
        {
            if (DateTime.Now > lastCacheManagementTime.AddMilliseconds(runCacheManagementInterval))
            {
                lastCacheManagementTime = DateTime.Now;
                ThreadPool.QueueUserWorkItem(__RunCacheManagementImpl);
            }
        }

        private static void __RunCacheManagementImpl(object args)
        {
            Guid eventId = Guid.NewGuid();  //And id for the task. Used for logging results/errors.
            DiscardUnusedLocks(eventId);
            DiscardUnusedCacheEntries(eventId);                        
        }

        private static void DiscardUnusedLocks(Guid eventId)
        {
            List<string> recoveryList = new List<string>();
            lock (globalLock)
            {
                foreach (KeyValuePair<string, LockInfo> pair in managedLocks)
                {
                    if (DateTime.Now > pair.Value.LockRequestTime.AddMilliseconds(discardLockPeriod))
                        recoveryList.Add(pair.Key);
                }
                foreach (string key in recoveryList)
                {
                    managedLocks.Remove(key);
                }
            }
            //Logger logger = new XmlLogger();
            //logger.WriteToSystemLog(eventId, "__DiscardUnusedLocks__NumLocks:" + managedLocks.Count.ToString() + " locks."); 
            //logger.WriteToSystemLog(eventId, "__DiscardUnusedLocks__Discarded " + recoveryList.Count.ToString() + " locks.");
        }

        private static void DiscardUnusedCacheEntries(Guid eventId)
        {
            List<string> recoveryList = new List<string>();
            lock (globalLock)
            {
                foreach (KeyValuePair<string, FileInfo> pair in managedFiles)
                {
                    if (DateTime.Now > pair.Value.CachedEntryAccessTime.AddMilliseconds(discardCacheEntryPeriod))
                        recoveryList.Add(pair.Key);
                }
                foreach (string key in recoveryList)
                {
                    managedFiles.Remove(key);
                }
            }
            //Logger logger = new XmlLogger();
            //logger.WriteToSystemLog(eventId, "__DiscardUnusedCacheEntries__NumCacheEntries:" + managedFiles.Count.ToString() + " entries."); 
            //logger.WriteToSystemLog(eventId, "__DiscardUnusedCacheEntries__Discarded " + recoveryList.Count.ToString() + " entries.");
        }
    }
}